package semantic;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import parser.Parser;

public class SemanticImpl implements Semantic{

	private Map<Integer, Object[]> areaInstrucoes;
	
	private ContextoEnum contexto;
	private int VT = 0;
	private int VP = 0;
	private TipoEnum tipo;
	private Stack<Integer> pilhaDesvios = new Stack<>();
	private int ponteiro = 1;
	private Map<String, Object[]> tabelaSimbolos = new HashMap<String, Object[]>();
	
	private String identificador;
	private Object valor;
	private List<String> erro = new ArrayList<String>();
	
	private Parser parser;

	public SemanticImpl(Parser parser) {
		super();
		this.areaInstrucoes = new HashMap<Integer, Object[]>();
		this.parser = parser;
	}

	@Override
	public Map<Integer, Object[]> getAreaInstrucoes() {
		return areaInstrucoes;
	}

	@Override
	public void generateInst(int num, CodInstrEnum codInst, Object par) {
		areaInstrucoes.put(num, new Object[]{codInst, par});

	}

	@Override
	public void updateInst(Integer num, int par) {
		Object[] aux = areaInstrucoes.get(num);
		areaInstrucoes.put(num, new Object[]{aux[0], par});
	}
	
	private enum ContextoEnum {
		VARIAVEL, CONSTANTE, ENTRADA_DADOS;
	}

	private enum TipoEnum {
		PLV_RESERVADA(0), VAR_INTEIRO(1), VAR_REAL(2), VAR_LITERAL(3), VAR_LOGICO(
				4), CONST_INTEIRO(5), CONST_REAL(6), CONST_LITERAL(7);

		public final int id;

		TipoEnum(int id) {
			this.id = id;
		}

		public boolean isVariavel() {

			return this.equals(VAR_INTEIRO) || this.equals(VAR_REAL)
					|| this.equals(VAR_LITERAL) || this.equals(VAR_LOGICO);
		}

		public boolean isConstante() {
			return this.equals(CONST_INTEIRO) || this.equals(CONST_REAL)
					|| this.equals(CONST_LITERAL);
		}
	}

	@Override
	public void action1() {
		generateInst(ponteiro, CodInstrEnum.STP, null);

	}

	@Override
	public void action2() {
		identificador = parser.token.image;
		tabelaSimbolos.put(identificador, new Object[] {
				TipoEnum.PLV_RESERVADA, null });

	}

	@Override
	public void action3() {
		contexto = ContextoEnum.CONSTANTE;

	}

	@Override
	public void action4() {
		switch (tipo) {
		case VAR_INTEIRO:
		case CONST_INTEIRO:
			generateInst(ponteiro, CodInstrEnum.ALI, VP);
			ponteiro++;
			break;
		case VAR_REAL:
		case CONST_REAL:
			generateInst(ponteiro, CodInstrEnum.ALR, VP);
			ponteiro++;
			break;
		case VAR_LITERAL:
		case CONST_LITERAL:
			generateInst(ponteiro, CodInstrEnum.ALS, VP);
			ponteiro++;
			break;
		case VAR_LOGICO:
			generateInst(ponteiro, CodInstrEnum.ALB, VP);
			ponteiro++;
			break;
		}
		if (tipo.isVariavel()) {
			VP = 0;
		}

	}

	@Override
	public void action5() {
		valor = parser.token.image;
		switch (tipo) {
		case CONST_INTEIRO:
			generateInst(ponteiro, CodInstrEnum.LDI, valor);
			ponteiro++;
			break;
		case CONST_REAL:
			generateInst(ponteiro, CodInstrEnum.LDR, valor);
			ponteiro++;
			break;
		case CONST_LITERAL:
			generateInst(ponteiro, CodInstrEnum.LDS, ((String)valor).replaceAll("\"", ""));
			ponteiro++;
			break;
		default:
			break;
		}

		generateInst(ponteiro, CodInstrEnum.STC, VP);

		ponteiro++;
		VP = 0;

	}

	@Override
	public void action6() {
		contexto = ContextoEnum.VARIAVEL;

	}

	@Override
	public void action7() {
		if (contexto.equals("variável")) {
			tipo = TipoEnum.VAR_INTEIRO;
		} else {
			tipo = TipoEnum.CONST_INTEIRO;
		}

	}

	@Override
	public void action8() {
		if (contexto.equals("variável")) {
			tipo = TipoEnum.VAR_REAL;
		} else {
			tipo = TipoEnum.CONST_REAL;
		}

	}

	@Override
	public void action9() {
		if (contexto.equals("variável")) {
			tipo = TipoEnum.VAR_LITERAL;
		} else {
			tipo = TipoEnum.CONST_LITERAL;
		}

	}

	@Override
	public void action10() {
		if (contexto.equals("variável")) {
			tipo = TipoEnum.VAR_LOGICO;
		} else {
			erro.add("tipo inválido para constante");
		}

	}

	@Override
	public void action11() {
		identificador = parser.token.image;
		switch (contexto) {
		case CONSTANTE:
		case VARIAVEL:
			if (tabelaSimbolos.containsKey(identificador)) {
				erro.add("identificador já declarado");
			} else {
				VT++;
				VP++;
				tabelaSimbolos.put(identificador, new Object[] { tipo, VT });
			}
			break;
		case ENTRADA_DADOS:
			if (tabelaSimbolos.containsKey(identificador)) {
				Object[] aux = tabelaSimbolos.get(identificador);
				TipoEnum tipoAux = (TipoEnum) aux[0];
				if (tipoAux.isVariavel()) {
					generateInst(ponteiro, CodInstrEnum.REA, tipoAux.id);
					ponteiro++;
					Object atributoAux = aux[1];
					generateInst(ponteiro, CodInstrEnum.STR, atributoAux);
					ponteiro++;
				} else {
					erro.add("identificador de programa ou de constante");
				}
			} else {
				erro.add("identificador não declarado");
			}
			break;

		default:
			break;
		}

	}

	@Override
	public void action12() {
		identificador = parser.token.image;
		if (tabelaSimbolos.containsKey(identificador)) {
			Object[] aux = tabelaSimbolos.get(identificador);
			TipoEnum tipoAux = (TipoEnum) aux[0];
			if (tipoAux.isVariavel()) {
				Object atributoAux = aux[1];
				generateInst(ponteiro, CodInstrEnum.STR, atributoAux);
				ponteiro++;
			} else {
				erro.add("identificador de programa ou de constante");
			}
		} else {
			erro.add("identificador não declarado");
		}
	}

	@Override
	public void action13() {
		contexto = ContextoEnum.ENTRADA_DADOS;

	}

	@Override
	public void action14() {
		generateInst(ponteiro, CodInstrEnum.WRT, null);
		ponteiro++;

	}

	@Override
	public void action15() {
		identificador = parser.token.image;
		if (tabelaSimbolos.containsKey(identificador)) {
			Object[] aux = tabelaSimbolos.get(identificador);
			TipoEnum tipoAux = (TipoEnum) aux[0];
			if (tipoAux.isVariavel() || tipoAux.isConstante()) {
				Object atributoAux = aux[1];
				generateInst(ponteiro, CodInstrEnum.LDV, atributoAux);
				ponteiro++;
			} else {
				erro.add("identificador de programa");
			}
		} else {
			erro.add("identificador nao declarado");
		}

	}

	@Override
	public void action16() {
		valor = parser.token.image;
		generateInst(ponteiro, CodInstrEnum.LDI, valor);
		ponteiro++;

	}

	@Override
	public void action17() {
		valor = parser.token.image;
		generateInst(ponteiro, CodInstrEnum.LDR, valor);
		ponteiro++;

	}

	@Override
	public void action18() {
		valor = parser.token.image;
		generateInst(ponteiro, CodInstrEnum.LDS, valor);
		ponteiro++;

	}

	@Override
	public void action19() {
		generateInst(ponteiro, CodInstrEnum.LDB, true);
		ponteiro++;

	}

	@Override
	public void action20() {
		generateInst(ponteiro, CodInstrEnum.LDB, false);
		ponteiro++;

	}

	@Override
	public void action21() {
		generateInst(ponteiro, CodInstrEnum.JMF, "?");
		ponteiro++;
		pilhaDesvios.add(ponteiro - 1);

	}

	@Override
	public void action22() {
		Integer posDesv = pilhaDesvios.pop();
		updateInst(posDesv, ponteiro);

	}

	@Override
	public void action23() {
		Integer posDesv = pilhaDesvios.pop();
		updateInst(posDesv, ponteiro + 1);
		generateInst(ponteiro, CodInstrEnum.JMP, "?");
		ponteiro++;
		pilhaDesvios.add(ponteiro - 1);
	}

	@Override
	public void action24() {
		pilhaDesvios.add(ponteiro);
	}

	@Override
	public void action25() {
		generateInst(ponteiro, CodInstrEnum.JMF, "?");
		ponteiro++;
		pilhaDesvios.add(ponteiro - 1);
	}

	@Override
	public void action26() {
		Integer posDesv = pilhaDesvios.pop();
		updateInst(posDesv, ponteiro + 1);
		posDesv = pilhaDesvios.pop();
		generateInst(ponteiro, CodInstrEnum.JMP, posDesv);
		ponteiro++;

	}

	@Override
	public void action27() {
		generateInst(ponteiro, CodInstrEnum.EQL, null);
		ponteiro++;
	}

	@Override
	public void action28() {
		generateInst(ponteiro, CodInstrEnum.DIF, null);
		ponteiro++;

	}

	@Override
	public void action29() {
		generateInst(ponteiro, CodInstrEnum.SMR, null);
		ponteiro++;

	}

	@Override
	public void action30() {
		generateInst(ponteiro, CodInstrEnum.BGR, null);
		ponteiro++;

	}

	@Override
	public void action31() {
		generateInst(ponteiro, CodInstrEnum.SME, null);
		ponteiro++;

	}

	@Override
	public void action32() {
		generateInst(ponteiro, CodInstrEnum.BGE, null);
		ponteiro++;

	}

	@Override
	public void action33() {
		generateInst(ponteiro, CodInstrEnum.ADD, null);
		ponteiro++;
	}

	@Override
	public void action34() {
		generateInst(ponteiro, CodInstrEnum.SUB, null);
		ponteiro++;
	}

	@Override
	public void action35() {
		generateInst(ponteiro, CodInstrEnum.OR, null);
		ponteiro++;

	}

	@Override
	public void action36() {
		generateInst(ponteiro, CodInstrEnum.MUL, null);
		ponteiro++;

	}

	@Override
	public void action37() {
		generateInst(ponteiro, CodInstrEnum.DIV, null);
		ponteiro++;

	}

	@Override
	public void action38() {
		generateInst(ponteiro, CodInstrEnum.DII, null);
		ponteiro++;

	}

	@Override
	public void action39() {
		generateInst(ponteiro, CodInstrEnum.MOD, null);
		ponteiro++;

	}

	@Override
	public void action40() {
		generateInst(ponteiro, CodInstrEnum.ADD, null);
		ponteiro++;

	}

	@Override
	public void action41() {
		generateInst(ponteiro, CodInstrEnum.POW, null);
		ponteiro++;

	}

	@Override
	public void action42() {
		generateInst(ponteiro, CodInstrEnum.NOT, null);
		ponteiro++;

	}

	@Override
	public List<String> getErro() {
		return erro;
	}

}
